User.seq다. 한 사람이 여러 Google 이메일로 로그인하더라도 같은 User.seq에 연결되면 같은 사용자로 취급한다.UserEmail이 관리한다. 이메일은 로그인 수단이자 권한 actor 후보로만 사용한다.UserEmail.isPrimary = true로 표현한다.User row는 주요 FK와 UserEmail을 canonical User.seq로 이동한 뒤 삭제한다.User 모델은 이메일을 직접 갖지 않고, UserEmail 모델이 이메일 조회, 추가, primary 설정, 중복 확인을 담당한다. 로그인은 UserEmail.email로 user를 찾고, 없으면 새 User와 첫 UserEmail을 만든다.seq, nickname, loginEmail 중심으로 정리했다. identity 판단은 seq로 하고, loginEmail은 표시나 감사 목적의 보조 정보다. 다만 UserEmail 행이 하나도 없는 사용자에게는 WikiPermission 이 loginEmail 을 권한 actor 로 대신 쓴다.User(seq) 참조 FK를 읽어 duplicate user의 참조를 canonical user로 옮긴다. UserEmail도 canonical user로 이동하고, 이동이 끝나면 duplicate User row를 삭제한다. 다만 (user, site) 유니크 키를 가진 테이블(UserSite·SiteAdmin)은 그대로 옮기면 키가 충돌하므로, canonical 이 이미 가진 site 는 duplicate 행을 지우고 나머지만 옮긴다.Permission은 현재 user의 모든 로그인 이메일을 actor 후보로 사용하도록 변경했다. Exact, Domain, Login 권한이 여러 로그인 이메일 기준으로 동작한다.UserMergeSpec가 FK 이동(Page, AccessLog), UserEmail 이동, primary 하나 유지, duplicate User 삭제, 그리고 두 사용자가 같은 site 의 관리자일 때 SiteAdmin 행이 충돌 없이 하나로 합쳐지는 것을 검증한다. (user, site) 충돌 처리는 UserMerge.mergeUniqueUserSiteRows 하나로 UserSite·SiteAdmin 에 함께 쓴다 — UserSite 는 evolution 55 가 지웠지만 테이블이 있을 때만 도는 가드 뒤에 남아 있고, SiteAdmin 은 2026-09-15 부터 같은 처리를 받는다. 전에는 SiteAdmin 겹침(둘 다 같은 site 관리자)이 primary key 를 위반해 병합 전체를 실패시켰다.배포 전에는 DB를 백업하고 staging에서 migration rehearsal을 수행한다. UserEmail.email unique 제약, 기존 user 수와 migration된 primary email 수, 현재 DB의 User(seq) 참조 FK 목록, Google OAuth Console의 /google/oauth/callback redirect URI 등록 여부를 확인한다.
배포 후에는 대표 계정의 여러 이메일로 각각 로그인하고, 관리자 권한, 이메일 기반 page permission, 계정 설정의 로그인 이메일 목록, 병합 flow를 확인한다.
Similar pages by cosine similarity. Words after page name are term frequency.